#include <map>
#include <string>

#include <endian.h>

#include "helper.hpp"
#include "interface.hpp"
#include "fingerprint.hpp"

extern std::map<std::string, Serial_info> port_map;

const char* fp_code_to_str(int code)
{
    switch(code) {
        case CHECK_ID               : return "Check if the requested user ID exists.";
        case ADD_NEW                : return "Adding more fingerprints to a current existing user ID.";
        case CONTINUE               : return "There is more data to be sent.";
        case AUTO_ID                : return "Automatically assign user ID in enrollment.";
        case CHECK_FINGER           : return "Check if the finger is already enrolled.";
        case CHECK_FINGER_AUTO_ID   : return "Check if the finger is already enrolled. If not, assign user ID automatically.";
        case ADD_DURESS             : return "Add duress fingerprints to an existing user ID.";
        case SUCCESS                : return "Successfully completed.";
        case SCAN_SUCCESS           : return "Fingerprint input has succeeded.";
        case SCAN_FAIL              : return "Sensor or fingerprint input has failed.";
        case NOT_FOUND              : return "There is no requested data found.";
        case NOT_MATCH              : return "Fingerprint does not match.";
        case TRY_AGAIN              : return "Fingerprint image is not good.";
        case TIME_OUT               : return "Timeout for fingerprint input.";
        case MEM_FULL               : return "Maximum template capacity exceeded.";
        case EXIST_ID               : return "The requested user ID exists.";
        case FINGER_LIMIT           : return "The number of fingerprints enrolled in same ID exceeds its limit (10).";
        case UNSUPPORTED            : return "The command is not supported.";
        case INVALID_ID             : return "The requested user ID is invalid or missing.";
        case TIMEOUT_MATCH          : return "Timeout for matching in identification.";
        case BUSY                   : return "Module is processing another command.";
        case CANCELED               : return "The command is canceled.";
        case DATA_ERROR             : return "The checksum of a data packet is incorrect.";
        case DATA_OK                : return "The checksum of a data packet is correct.";
        case EXIST_FINGER           : return "The finger is already enrolled.";
        case DURESS_FINGER          : return "A duress finger is detected.";
        case LOCKED                 : return "Module is locked.";
        case REJECTED_ID            : return "Authentication mode of the user is AUTH_REJECT or the ID is in the blacklist.";
        case ENTRANCE_LIMIT         : return "Authentication fails since the entrance limit is exceeded.";
        default: return "UNKNOWN CODE";
    }
}

fp_packet_t fp_send_request(const fp_packet_t req)
{	
    int rv;
    fd_set readfds;
    fp_packet_t resp = {0x0};
    //std::string port = "/dev/ttyUSB0";
    std::string port = "/dev/fingerprint";
    int fd = port_map[port].s_fd;
    struct timeval timeout = { .tv_sec = 0, .tv_usec = 10000000 }; // set fingerprint read timeout to 10 sec

    /* lock the corresponding mutex for I/O. */
    if(pthread_mutex_lock(&port_map[port].s_mutex) != 0){
        perror("pthread_mutex_lock");
        return resp;
    }

    do {
        rv = write(fd, &req, sizeof(fp_packet_t));

        if(rv != sizeof(fp_packet_t)){
            logger(SM_LOG_ERR,"FINGERPRINT: unable to send req [%s], port = %s\n", 
                    fp_pkt_to_str(req).c_str(), port.c_str());
            break;
        }

        int read_count = 1;
read_again:
        FD_ZERO(&readfds);
        FD_SET(fd, &readfds);

        rv = select(fd + 1, &readfds, NULL, NULL, &timeout);

        if(rv == -1) {
            perror("select");
            break;
        } else if(rv == 0){
            logger(SM_LOG_ERR,"FINGERPRINT: failed to read between specified timeout [%s], port = %s\n", 
                    fp_pkt_to_str(req).c_str(), port.c_str());
            break;
        }

        rv = read(fd, &resp, sizeof(resp));
        if(rv != sizeof(fp_packet_t)){
            logger(SM_LOG_ERR,"FINGERPRINT: unable to read resp [%s], port = %s\n", 
                    fp_pkt_to_str(resp).c_str(), port.c_str());
            break;
        }

        logger(SM_LOG_INFO,"Fingerprint: [%s]\n", fp_pkt_to_str(resp).c_str());
    
        if(req.command == 0x05 && read_count != 0) {
            read_count--;
            goto read_again;
        }
    } 
    while(0);

    if(pthread_mutex_unlock(&port_map[port].s_mutex) != 0){
        perror("pthread_mutex_unlock");
    }

    return resp;
}

static uint8_t fp_calc_csum(const fp_packet_t pkt)
{
    uint8_t csum = 
        (pkt.start_code + 
         pkt.terminal_id[0] + 
         pkt.terminal_id[1] + 
         pkt.command + 
         pkt.param[0] + 
         pkt.param[1] + 
         pkt.param[2] + 
         pkt.param[3] + 
         pkt.size[0] + 
         pkt.size[1] + 
         pkt.size[2] + 
         pkt.size[3] + 
         pkt.flag_error) % 0x100;

        return csum;
}

/* initialize fingerprint module */
int fp_init()
{
    fp_packet_t req, resp;
    req.start_code      = 0x41;
    req.terminal_id[0]  = 0x00;
    req.terminal_id[1]  = 0x00;
    req.command         = 0x01;
    req.param[0]        = 0x00;
    req.param[1]        = 0x00;
    req.param[2]        = 0x00;
    req.param[3]        = 0x00;
    req.size[0]         = 0x31;
    req.size[1]         = 0x00;
    req.size[2]         = 0x00;
    req.size[3]         = 0x00;
    req.flag_error      = 0x83;
    req.csum            = fp_calc_csum(req);
    req.end_code        = 0x0A;

    /*
     *req.start_code      = 0x41;
     *req.terminal_id[0]  = 0x0;
     *req.terminal_id[1]  = 0x0;
     *req.command         = 0x01;
     *req.param[0]        = 0x00;
     *req.param[1]        = 0x00;
     *req.param[2]        = 0x00;
     *req.param[3]        = 0x00;
     *req.size[0]         = 0x30;
     *req.size[1]         = 0x00;
     *req.size[2]         = 0x00;
     *req.size[3]         = 0x00;
     *req.flag_error      = 0x75;
     *req.csum            = fp_calc_csum(req);
     *req.end_code        = 0x0A;
     */

    /* setting 'Send Scan Success' system parameter to 0x30 (No SCAN_SUCCESS) */
    resp = fp_send_request(req);
    if(resp.flag_error != SUCCESS) {
        logger(SM_LOG_ERR,"FINGERPRINT: %s\n", fp_code_to_str(resp.flag_error));
        return -1;
    }

    return 0;
}

/* enroll new finger and return it's id or -1 in case of failure*/
int fp_enroll() 
{
    int rv = -1;
    fp_packet_t req, resp;
    req.start_code      = 0x40;
    req.terminal_id[0]  = 0x0;
    req.terminal_id[1]  = 0x0;
    req.command         = 0x05;
    req.param[0]        = 0x00;
    req.param[1]        = 0x00;
    req.param[2]        = 0x00;
    req.param[3]        = 0x00;
    req.size[0]         = 0x00;
    req.size[1]         = 0x00;
    req.size[2]         = 0x00;
    req.size[3]         = 0x00;
    req.flag_error      = AUTO_ID;
    req.csum            = fp_calc_csum(req);
    req.end_code        = 0x0A;

    resp = fp_send_request(req);
    if(resp.flag_error == SUCCESS) {
        rv = le32toh(*((uint32_t*)resp.param));
    }

    return rv;
}

/* turn identification mode and wait for scan and verification, return id or -1
 * in case of failure*/
int fp_identify() 
{
    int rv = -1;
    fp_packet_t req, resp;
    req.start_code      = 0x41;
    req.terminal_id[0]  = 0x0;
    req.terminal_id[1]  = 0x0;
    req.command         = 0x11;
    req.param[0]        = 0x00;
    req.param[1]        = 0x00;
    req.param[2]        = 0x00;
    req.param[3]        = 0x00;
    req.size[0]         = 0x00;
    req.size[1]         = 0x00;
    req.size[2]         = 0x00;
    req.size[3]         = 0x00;
    req.flag_error      = 0x00;
    req.csum            = fp_calc_csum(req);
    req.end_code        = 0x0A;

    resp = fp_send_request(req);
    if(resp.flag_error == SUCCESS) {
        rv = le32toh(*((uint32_t*)resp.param));
    }

    return rv;
}

/* delete enrolled template by scan, return 0 or -1 in case of failure */
int fp_delete()
{
    int rv = -1;
    fp_packet_t req, resp;
    req.start_code      = 0x40;
    req.terminal_id[0]  = 0x0;
    req.terminal_id[1]  = 0x0;
    req.command         = 0x1E;
    req.param[0]        = 0x00;
    req.param[1]        = 0x00;
    req.param[2]        = 0x00;
    req.param[3]        = 0x00;
    req.size[0]         = 0x00;
    req.size[1]         = 0x00;
    req.size[2]         = 0x00;
    req.size[3]         = 0x00;
    req.flag_error      = 0x00;
    req.csum            = fp_calc_csum(req);
    req.end_code        = 0x0A;

    resp = fp_send_request(req);
    if(resp.flag_error == SUCCESS) {
        rv = le32toh(*((uint32_t*)resp.param));
    } else {
        logger(SM_LOG_ERR,"FINGERPRINT: can't delete fingerprint, template not found\n");
    }

    return rv;
}

/* change template level, return 0 or -1 in case of failure */
int fp_change_level(int id, int level)
{
    int rv = -1;
    return rv;
}
